/* PDCurses */

#include "pdcwin.h"

#include <stdlib.h>

/* Color component table */

PDCCOLOR pdc_color[PDC_MAXCOL];

HANDLE std_con_out = INVALID_HANDLE_VALUE;
HANDLE pdc_con_out = INVALID_HANDLE_VALUE;
HANDLE pdc_con_in = INVALID_HANDLE_VALUE;

DWORD pdc_quick_edit;

static short realtocurs[16] =
{
	COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN, COLOR_RED,
	COLOR_MAGENTA, COLOR_YELLOW, COLOR_WHITE, COLOR_BLACK + 8,
	COLOR_BLUE + 8, COLOR_GREEN + 8, COLOR_CYAN + 8, COLOR_RED + 8,
	COLOR_MAGENTA + 8, COLOR_YELLOW + 8, COLOR_WHITE + 8
};

static short ansitocurs[16] =
{
	COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE,
	COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE, COLOR_BLACK + 8,
	COLOR_RED + 8, COLOR_GREEN + 8, COLOR_YELLOW + 8, COLOR_BLUE + 8,
	COLOR_MAGENTA + 8, COLOR_CYAN + 8, COLOR_WHITE + 8
};

short pdc_curstoreal[16], pdc_curstoansi[16];
short pdc_oldf, pdc_oldb, pdc_oldu;
bool pdc_conemu, pdc_wt, pdc_ansi;

enum { PDC_RESTORE_NONE, PDC_RESTORE_BUFFER };

/* Struct for storing console registry keys, and for use with the
   undocumented WM_SETCONSOLEINFO message. Originally by James Brown,
   www.catch22.net. */

static struct
{
	ULONG    Length;
	COORD    ScreenBufferSize;
	COORD    WindowSize;
	ULONG    WindowPosX;
	ULONG    WindowPosY;

	COORD    FontSize;
	ULONG    FontFamily;
	ULONG    FontWeight;
	WCHAR    FaceName[32];

	ULONG    CursorSize;
	ULONG    FullScreen;
	ULONG    QuickEdit;
	ULONG    AutoPosition;
	ULONG    InsertMode;

	USHORT   ScreenColors;
	USHORT   PopupColors;
	ULONG    HistoryNoDup;
	ULONG    HistoryBufferSize;
	ULONG    NumberOfHistoryBuffers;

	COLORREF ColorTable[16];

	ULONG    CodePage;
	HWND     Hwnd;

	WCHAR    ConsoleTitle[0x100];
} console_info;

#ifdef HAVE_NO_INFOEX
/* Console screen buffer information (extended version) */
typedef struct _CONSOLE_SCREEN_BUFFER_INFOEX
{
	ULONG       cbSize;
	COORD       dwSize;
	COORD       dwCursorPosition;
	WORD        wAttributes;
	SMALL_RECT  srWindow;
	COORD       dwMaximumWindowSize;
	WORD        wPopupAttributes;
	BOOL        bFullscreenSupported;
	COLORREF    ColorTable[16];
} CONSOLE_SCREEN_BUFFER_INFOEX;
typedef CONSOLE_SCREEN_BUFFER_INFOEX*    PCONSOLE_SCREEN_BUFFER_INFOEX;
#endif

typedef BOOL ( WINAPI* SetConsoleScreenBufferInfoExFn )( HANDLE hConsoleOutput,
		PCONSOLE_SCREEN_BUFFER_INFOEX lpConsoleScreenBufferInfoEx );
typedef BOOL ( WINAPI* GetConsoleScreenBufferInfoExFn )( HANDLE hConsoleOutput,
		PCONSOLE_SCREEN_BUFFER_INFOEX lpConsoleScreenBufferInfoEx );

static SetConsoleScreenBufferInfoExFn pSetConsoleScreenBufferInfoEx = NULL;
static GetConsoleScreenBufferInfoExFn pGetConsoleScreenBufferInfoEx = NULL;

static CONSOLE_SCREEN_BUFFER_INFO orig_scr;
static CONSOLE_SCREEN_BUFFER_INFOEX console_infoex;

static LPTOP_LEVEL_EXCEPTION_FILTER xcpt_filter;

static DWORD old_console_mode = 0;

static bool is_nt;

static void _reset_old_colors( void )
{
	pdc_oldf = -1;
	pdc_oldb = -1;
	pdc_oldu = 0;
}

static HWND _find_console_handle( void )
{
	TCHAR orgtitle[1024], temptitle[1024];
	HWND wnd;

	GetConsoleTitle( orgtitle, 1024 );

	wsprintf( temptitle, TEXT( "%d/%d" ), GetTickCount(), GetCurrentProcessId() );
	SetConsoleTitle( temptitle );

	Sleep( 40 );

	wnd = FindWindow( NULL, temptitle );

	SetConsoleTitle( orgtitle );

	return wnd;
}

/* Undocumented console message */

#define WM_SETCONSOLEINFO (WM_USER + 201)

/* Wrapper around WM_SETCONSOLEINFO. We need to create the necessary
   section (file-mapping) object in the context of the process which
   owns the console, before posting the message. Originally by JB. */

static void _set_console_info( void )
{
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	CONSOLE_CURSOR_INFO cci;
	DWORD dwConsoleOwnerPid;
	HANDLE hProcess;
	HANDLE hSection, hDupSection;
	PVOID ptrView;

	/* Each-time initialization for console_info */

	GetConsoleCursorInfo( pdc_con_out, &cci );
	console_info.CursorSize = cci.dwSize;

	GetConsoleScreenBufferInfo( pdc_con_out, &csbi );
	console_info.ScreenBufferSize = csbi.dwSize;

	console_info.WindowSize.X = csbi.srWindow.Right - csbi.srWindow.Left + 1;
	console_info.WindowSize.Y = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;

	console_info.WindowPosX = csbi.srWindow.Left;
	console_info.WindowPosY = csbi.srWindow.Top;

	/* Open the process which "owns" the console */

	GetWindowThreadProcessId( console_info.Hwnd, &dwConsoleOwnerPid );

	hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, dwConsoleOwnerPid );

	/* Create a SECTION object backed by page-file, then map a view of
	   this section into the owner process so we can write the contents
	   of the CONSOLE_INFO buffer into it */

	hSection = CreateFileMapping( INVALID_HANDLE_VALUE, 0, PAGE_READWRITE,
								  0, sizeof( console_info ), 0 );

	/* Copy our console structure into the section-object */

	ptrView = MapViewOfFile( hSection, FILE_MAP_WRITE | FILE_MAP_READ,
							 0, 0, sizeof( console_info ) );

	memcpy( ptrView, &console_info, sizeof( console_info ) );

	UnmapViewOfFile( ptrView );

	/* Map the memory into owner process */

	DuplicateHandle( GetCurrentProcess(), hSection, hProcess, &hDupSection,
					 0, FALSE, DUPLICATE_SAME_ACCESS );

	/* Send console window the "update" message */

	SendMessage( console_info.Hwnd, WM_SETCONSOLEINFO, ( WPARAM )hDupSection, 0 );

	CloseHandle( hSection );
	CloseHandle( hProcess );
}

static int _set_console_infoex( void )
{
	if( !pSetConsoleScreenBufferInfoEx( pdc_con_out, &console_infoex ) )
	{
		return ERR;
	}

	return OK;
}

static int _set_colors( void )
{
	SetConsoleTextAttribute( pdc_con_out, 7 );
	_reset_old_colors();

	if( pSetConsoleScreenBufferInfoEx )
	{
		return _set_console_infoex();
	}
	else
	{
		_set_console_info();
		return OK;
	}
}

/* One-time initialization for console_info -- color table and font info
   from the registry; other values from functions. */

static void _init_console_info( void )
{
	DWORD scrnmode, len;
	HKEY reghnd;
	int i;

	console_info.Hwnd = _find_console_handle();
	console_info.Length = sizeof( console_info );

	GetConsoleMode( pdc_con_in, &scrnmode );
	console_info.QuickEdit = !!( scrnmode & 0x0040 );
	console_info.InsertMode = !!( scrnmode & 0x0020 );

	console_info.FullScreen = FALSE;
	console_info.AutoPosition = 0x10000;
	console_info.ScreenColors = SP->orig_back << 4 | SP->orig_fore;
	console_info.PopupColors = 0xf5;

	console_info.HistoryNoDup = FALSE;
	console_info.HistoryBufferSize = 50;
	console_info.NumberOfHistoryBuffers = 4;

	console_info.CodePage = GetConsoleOutputCP();

	RegOpenKeyEx( HKEY_CURRENT_USER, TEXT( "Console" ), 0,
				  KEY_QUERY_VALUE, &reghnd );

	len = sizeof( DWORD );

	/* Default color table */

	for( i = 0; i < 16; i++ )
	{
		char tname[13];

		sprintf( tname, "ColorTable%02d", i );
		RegQueryValueExA( reghnd, tname, NULL, NULL,
						  ( LPBYTE )( &( console_info.ColorTable[i] ) ), &len );
	}

	/* Font info */

	RegQueryValueEx( reghnd, TEXT( "FontSize" ), NULL, NULL,
					 ( LPBYTE )( &console_info.FontSize ), &len );
	RegQueryValueEx( reghnd, TEXT( "FontFamily" ), NULL, NULL,
					 ( LPBYTE )( &console_info.FontFamily ), &len );
	RegQueryValueEx( reghnd, TEXT( "FontWeight" ), NULL, NULL,
					 ( LPBYTE )( &console_info.FontWeight ), &len );

	len = sizeof( WCHAR ) * 32;
	RegQueryValueExW( reghnd, L"FaceName", NULL, NULL,
					  ( LPBYTE )( console_info.FaceName ), &len );

	RegCloseKey( reghnd );
}

static int _init_console_infoex( void )
{
	console_infoex.cbSize = sizeof( console_infoex );

	if( !pGetConsoleScreenBufferInfoEx( pdc_con_out, &console_infoex ) )
	{
		return ERR;
	}

	console_infoex.srWindow.Right++;
	console_infoex.srWindow.Bottom++;

	return OK;
}

static COLORREF* _get_colors( void )
{
	if( pGetConsoleScreenBufferInfoEx )
	{
		int status = OK;
		if( !console_infoex.cbSize )
		{
			status = _init_console_infoex();
		}
		return ( status == ERR ) ? NULL :
			   ( COLORREF* )( &( console_infoex.ColorTable ) );
	}
	else
	{
		if( !console_info.Hwnd )
		{
			_init_console_info();
		}
		return ( COLORREF* )( &( console_info.ColorTable ) );
	}
}

/* restore the original console buffer in the event of a crash */

static LONG WINAPI _restore_console( LPEXCEPTION_POINTERS ep )
{
	PDC_scr_close();

	return EXCEPTION_CONTINUE_SEARCH;
}

/* restore the original console buffer on Ctrl+Break (or Ctrl+C,
   if it gets re-enabled) */

static BOOL WINAPI _ctrl_break( DWORD dwCtrlType )
{
	if( dwCtrlType == CTRL_BREAK_EVENT || dwCtrlType == CTRL_C_EVENT )
	{
		PDC_scr_close();
	}

	return FALSE;
}

/* close the physical screen -- may restore the screen to its state
   before PDC_scr_open(); miscellaneous cleanup */

void PDC_scr_close( void )
{
	PDC_LOG( ( "PDC_scr_close() - called\n" ) );

	if( SP->visibility != 1 )
	{
		curs_set( 1 );
	}

	PDC_reset_shell_mode();

	/* Position cursor to the bottom left of the screen. */

	if( SP->_restore == PDC_RESTORE_NONE )
	{
		SMALL_RECT win;

		win.Left = orig_scr.srWindow.Left;
		win.Right = orig_scr.srWindow.Right;
		win.Top = 0;
		win.Bottom = orig_scr.srWindow.Bottom - orig_scr.srWindow.Top;
		SetConsoleWindowInfo( pdc_con_out, TRUE, &win );
		PDC_gotoyx( win.Bottom, 0 );
	}
}

void PDC_scr_free( void )
{
	if( pdc_con_out != std_con_out )
	{
		CloseHandle( pdc_con_out );
		pdc_con_out = std_con_out;
	}

	SetUnhandledExceptionFilter( xcpt_filter );
	SetConsoleCtrlHandler( _ctrl_break, FALSE );
}

/* open the physical screen -- miscellaneous initialization, may save
   the existing screen for later restoration */

int PDC_scr_open( void )
{
	const char* str;
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	HMODULE h_kernel;
	BOOL result;
	int i;

	PDC_LOG( ( "PDC_scr_open() - called\n" ) );

	for( i = 0; i < 16; i++ )
	{
		pdc_curstoreal[realtocurs[i]] = i;
		pdc_curstoansi[ansitocurs[i]] = i;
	}
	_reset_old_colors();

	std_con_out =
		pdc_con_out = GetStdHandle( STD_OUTPUT_HANDLE );
	pdc_con_in = GetStdHandle( STD_INPUT_HANDLE );

	if( GetFileType( pdc_con_in ) != FILE_TYPE_CHAR )
	{
		fprintf( stderr, "\nRedirection is not supported.\n" );
		exit( 1 );
	}

	is_nt = !( GetVersion() & 0x80000000 );

	pdc_wt = !!getenv( "WT_SESSION" );
	str = pdc_wt ? NULL : getenv( "ConEmuANSI" );
	pdc_conemu = !!str;
	pdc_ansi = pdc_wt ? TRUE : pdc_conemu ? !strcmp( str, "ON" ) : FALSE;

	GetConsoleScreenBufferInfo( pdc_con_out, &csbi );
	GetConsoleScreenBufferInfo( pdc_con_out, &orig_scr );
	GetConsoleMode( pdc_con_in, &old_console_mode );

	/* preserve QuickEdit Mode setting for use in PDC_mouse_set() when
	   the mouse is not enabled -- other console input settings are
	   cleared */

	pdc_quick_edit = old_console_mode & 0x0040;

	SP->mouse_wait = PDC_CLICK_PERIOD;
	SP->audible = TRUE;

	SP->termattrs = A_COLOR | A_REVERSE;
	if( pdc_ansi )
	{
		SP->termattrs |= A_UNDERLINE | A_ITALIC;
	}

	SP->orig_fore = csbi.wAttributes & 0x0f;
	SP->orig_back = ( csbi.wAttributes & 0xf0 ) >> 4;

	SP->orig_attr = TRUE;

	SP->_restore = PDC_RESTORE_NONE;

	if( ( str = getenv( "PDC_RESTORE_SCREEN" ) ) == NULL || *str != '0' )
	{
		/* Create a new console buffer */

		pdc_con_out =
			CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE,
									   FILE_SHARE_READ | FILE_SHARE_WRITE,
									   NULL, CONSOLE_TEXTMODE_BUFFER, NULL );

		if( pdc_con_out == INVALID_HANDLE_VALUE )
		{
			PDC_LOG( ( "PDC_scr_open() - screen buffer failure\n" ) );

			pdc_con_out = std_con_out;
		}
		else
		{
			SP->_restore = PDC_RESTORE_BUFFER;
		}
	}

	xcpt_filter = SetUnhandledExceptionFilter( _restore_console );
	SetConsoleCtrlHandler( _ctrl_break, TRUE );

	SP->_preserve = ( getenv( "PDC_PRESERVE_SCREEN" ) != NULL );

	/* ENABLE_LVB_GRID_WORLDWIDE */
	result = SetConsoleMode( pdc_con_out, 0x0010 );
	if( result )
	{
		SP->termattrs |= A_UNDERLINE | A_LEFT | A_RIGHT;
	}

	PDC_reset_prog_mode();

	SP->mono = FALSE;

	h_kernel = GetModuleHandleA( "kernel32.dll" );
	pGetConsoleScreenBufferInfoEx =
		( GetConsoleScreenBufferInfoExFn )GetProcAddress( h_kernel,
				"GetConsoleScreenBufferInfoEx" );
	pSetConsoleScreenBufferInfoEx =
		( SetConsoleScreenBufferInfoExFn )GetProcAddress( h_kernel,
				"SetConsoleScreenBufferInfoEx" );

	return OK;
}

/* Calls SetConsoleWindowInfo with the given parameters, but fits them
   if a scoll bar shrinks the maximum possible value. The rectangle
   must at least fit in a half-sized window. */

static BOOL _fit_console_window( HANDLE con_out, CONST SMALL_RECT* rect )
{
	SMALL_RECT run;
	SHORT mx, my;

	if( SetConsoleWindowInfo( con_out, TRUE, rect ) )
	{
		return TRUE;
	}

	run = *rect;
	run.Right /= 2;
	run.Bottom /= 2;

	mx = run.Right;
	my = run.Bottom;

	if( !SetConsoleWindowInfo( con_out, TRUE, &run ) )
	{
		return FALSE;
	}

	for( run.Right = rect->Right; run.Right >= mx; run.Right-- )
		if( SetConsoleWindowInfo( con_out, TRUE, &run ) )
		{
			break;
		}

	if( run.Right < mx )
	{
		return FALSE;
	}

	for( run.Bottom = rect->Bottom; run.Bottom >= my; run.Bottom-- )
		if( SetConsoleWindowInfo( con_out, TRUE, &run ) )
		{
			return TRUE;
		}

	return FALSE;
}

/* the core of resize_term() */

int PDC_resize_screen( int nlines, int ncols )
{
	SMALL_RECT rect;
	COORD size, max;

	bool prog_resize = nlines || ncols;

	if( !prog_resize )
	{
		nlines = PDC_get_rows();
		ncols = PDC_get_columns();
	}

	if( nlines < 2 || ncols < 2 )
	{
		return ERR;
	}

	max = GetLargestConsoleWindowSize( pdc_con_out );

	rect.Left = rect.Top = 0;
	rect.Right = ncols - 1;

	if( rect.Right > max.X )
	{
		rect.Right = max.X;
	}

	rect.Bottom = nlines - 1;

	if( rect.Bottom > max.Y )
	{
		rect.Bottom = max.Y;
	}

	size.X = rect.Right + 1;
	size.Y = rect.Bottom + 1;

	_fit_console_window( pdc_con_out, &rect );
	SetConsoleScreenBufferSize( pdc_con_out, size );

	if( prog_resize )
	{
		_fit_console_window( pdc_con_out, &rect );
		SetConsoleScreenBufferSize( pdc_con_out, size );
	}
	SetConsoleActiveScreenBuffer( pdc_con_out );

	PDC_flushinp();

	return OK;
}

void PDC_reset_prog_mode( void )
{
	PDC_LOG( ( "PDC_reset_prog_mode() - called.\n" ) );

	if( pdc_con_out != std_con_out )
	{
		SetConsoleActiveScreenBuffer( pdc_con_out );
	}
	else if( is_nt )
	{
		COORD bufsize;
		SMALL_RECT rect;

		bufsize.X = orig_scr.srWindow.Right - orig_scr.srWindow.Left + 1;
		bufsize.Y = orig_scr.srWindow.Bottom - orig_scr.srWindow.Top + 1;

		rect.Top = rect.Left = 0;
		rect.Bottom = bufsize.Y - 1;
		rect.Right = bufsize.X - 1;

		SetConsoleScreenBufferSize( pdc_con_out, bufsize );
		SetConsoleWindowInfo( pdc_con_out, TRUE, &rect );
		SetConsoleScreenBufferSize( pdc_con_out, bufsize );
		SetConsoleActiveScreenBuffer( pdc_con_out );
	}

	PDC_mouse_set();
}

void PDC_reset_shell_mode( void )
{
	PDC_LOG( ( "PDC_reset_shell_mode() - called.\n" ) );

	if( pdc_con_out != std_con_out )
	{
		SetConsoleActiveScreenBuffer( std_con_out );
	}
	else if( is_nt )
	{
		SetConsoleScreenBufferSize( pdc_con_out, orig_scr.dwSize );
		SetConsoleWindowInfo( pdc_con_out, TRUE, &orig_scr.srWindow );
		SetConsoleScreenBufferSize( pdc_con_out, orig_scr.dwSize );
		SetConsoleWindowInfo( pdc_con_out, TRUE, &orig_scr.srWindow );
		SetConsoleActiveScreenBuffer( pdc_con_out );
	}

	SetConsoleMode( pdc_con_in, old_console_mode | 0x0080 );
}

void PDC_restore_screen_mode( int i )
{
}

void PDC_save_screen_mode( int i )
{
}

bool PDC_can_change_color( void )
{
	return is_nt;
}

int PDC_color_content( short color, short* red, short* green, short* blue )
{
	if( color < 16 && !( pdc_conemu || pdc_wt ) )
	{
		COLORREF* color_table = _get_colors();

		if( color_table )
		{
			DWORD col = color_table[pdc_curstoreal[color]];

			*red = DIVROUND( GetRValue( col ) * 1000, 255 );
			*green = DIVROUND( GetGValue( col ) * 1000, 255 );
			*blue = DIVROUND( GetBValue( col ) * 1000, 255 );
		}
		else
		{
			return ERR;
		}
	}
	else
	{
		if( !pdc_color[color].mapped )
		{
			*red = *green = *blue = -1;
			return ERR;
		}

		*red = pdc_color[color].r;
		*green = pdc_color[color].g;
		*blue = pdc_color[color].b;
	}

	return OK;
}

int PDC_init_color( short color, short red, short green, short blue )
{
	if( red == -1 && green == -1 && blue == -1 )
	{
		pdc_color[color].mapped = FALSE;
		return OK;
	}

	if( color < 16 && !( pdc_conemu || pdc_wt ) )
	{
		COLORREF* color_table = _get_colors();

		if( color_table )
		{
			color_table[pdc_curstoreal[color]] =
				RGB( DIVROUND( red * 255, 1000 ),
					 DIVROUND( green * 255, 1000 ),
					 DIVROUND( blue * 255, 1000 ) );

			return _set_colors();
		}

		return ERR;
	}
	else
	{
		pdc_color[color].r = red;
		pdc_color[color].g = green;
		pdc_color[color].b = blue;
		pdc_color[color].mapped = TRUE;
	}

	return OK;
}
